package it.unibo.refolding.pico.mfp;

import it.unimi.dsi.fastutil.bytes.ByteList;
import it.unimi.dsi.fastutil.doubles.DoubleList;
import it.unimi.dsi.fastutil.ints.IntList;

import java.nio.ByteBuffer;
import java.util.List;

import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

public class WaveHeader5 extends WaveHeader {
  
  final IntList nDim;
  final DoubleList sfA;
  final DoubleList sfB;
  final List<ByteList> unsignedDimUnits;
  
  private static final int MAX_WAVE_NAME5 = 31;

  private static final int RESERVED_AT_END = IgorUtil.POINTER_SIZE + IgorUtil.POINTER_SIZE
    * IgorUtil.MAXDIMS * 2 + IgorUtil.POINTER_SIZE + 16 * 4 + 3 * 2 + 2 * 1 + IgorUtil.POINTER_SIZE
    + 4 + 2 * 2 + 2 * IgorUtil.POINTER_SIZE;

  public WaveHeader5(int unsignedCreationDate, int unsignedModDate, int npnts, short type,
    ByteList unsignedBname, IntList nDim, DoubleList sfA, DoubleList sfB,
    ByteList unsignedDataUnits, List<ByteList> unsignedDimUnits, short fsValid,
    double topFullScale, double botFullScale) {
    super(type, unsignedBname, unsignedDataUnits, npnts,
      unsignedCreationDate, unsignedModDate, fsValid, topFullScale,
      botFullScale);
    this.nDim = nDim;
    this.sfA = sfA;
    this.sfB = sfB;
    this.unsignedDimUnits = unsignedDimUnits;
  }
  
  @Override
  public boolean equalsCommonFields(WaveHeader other) {
    if (other instanceof WaveHeader5) {
      return equals(other);
    } else {
      return super.equalsCommonFields(other);
    }
  }
  
  static Checksummed<WaveHeader5> read(ByteBuffer bb) {
    int startPos = bb.position();
    int unsignedChecksum = 0;
    /*
     * Skip: struct WaveHeader5 **next; // link to next wave in linked list.
     */
    unsignedChecksum = IgorUtil.skip(IgorUtil.POINTER_SIZE, unsignedChecksum, bb);
    int unsignedCreationDate = bb.getInt();
    unsignedChecksum = IgorUtil.addInt(unsignedCreationDate, unsignedChecksum);
    int unsignedModDate = bb.getInt();
    unsignedChecksum = IgorUtil.addInt(unsignedModDate, unsignedChecksum);
    int npnts = bb.getInt();
    unsignedChecksum = IgorUtil.addInt(npnts, unsignedChecksum);
    short type = bb.getShort();
    unsignedChecksum = IgorUtil.addShort(type, unsignedChecksum);
    /*
     * Skip: short dLock; // Reserved. Write zero. Ignore on read. char
     * whpad1[6]; // Reserved. Write zero. Ignore on read. short whVersion; //
     * Write 1. Ignore on read.
     */
    unsignedChecksum = IgorUtil.skip(2 + 6 + 2, unsignedChecksum, bb);
    Checksummed<ByteList> checksummedUnsignedBname = IgorUtil.readString(
      WaveHeader5.MAX_WAVE_NAME5, bb);
    unsignedChecksum = IgorUtil.addShort(
      checksummedUnsignedBname.unsignedChecksum, unsignedChecksum);
    ByteList unsignedBname = checksummedUnsignedBname.object;
    /*
     * Skip: long whpad2; // Reserved. Write zero. Ignore on read. struct
     * DataFolder **dFolder; // Used in memory only. Write zero. Ignore on read.
     */
    unsignedChecksum = IgorUtil.skip(4 + IgorUtil.POINTER_SIZE, unsignedChecksum, bb);
    IntList nDim = IgorUtil.readInts(IgorUtil.MAXDIMS, bb);
    unsignedChecksum = IgorUtil.addInts(nDim, unsignedChecksum);
    DoubleList sfA = IgorUtil.readDoubles(IgorUtil.MAXDIMS, bb);
    unsignedChecksum = IgorUtil.addDoubles(sfA, unsignedChecksum);
    DoubleList sfB = IgorUtil.readDoubles(IgorUtil.MAXDIMS, bb);
    unsignedChecksum = IgorUtil.addDoubles(sfB, unsignedChecksum);
    Checksummed<ByteList> checksummedUnsignedDataUnits = IgorUtil.readString(
      IgorUtil.MAX_UNIT_CHARS, bb);
    unsignedChecksum = IgorUtil.addShort(
      checksummedUnsignedDataUnits.unsignedChecksum, unsignedChecksum);
    ByteList unsignedDataUnits = checksummedUnsignedDataUnits.object;
    Checksummed<List<ByteList>> checksummedUnsignedDimUnits = IgorUtil.readStrings(
      IgorUtil.MAXDIMS, IgorUtil.MAX_UNIT_CHARS, bb);
    unsignedChecksum = IgorUtil.addShort(
      checksummedUnsignedDimUnits.unsignedChecksum, unsignedChecksum);
    List<ByteList> unsignedDimUnits = checksummedUnsignedDimUnits.object;
    short fsValid = bb.getShort();
    unsignedChecksum = IgorUtil.addShort(fsValid, unsignedChecksum);
    /*
     * Skip: short whpad3; // Reserved. Write zero. Ignore on read.
     */
    unsignedChecksum = IgorUtil.skip(2, unsignedChecksum, bb);
    double topFullScale = bb.getDouble();
    unsignedChecksum = IgorUtil.addDouble(topFullScale, unsignedChecksum);
    double botFullScale = bb.getDouble();
    unsignedChecksum = IgorUtil.addDouble(botFullScale, unsignedChecksum);
    /*
     * Handle dataEUnits; // Used in memory only. Write zero. Ignore on read.
     * Handle dimEUnits[MAXDIMS]; // Used in memory only. Write zero. Ignore on
     * read. Handle dimLabels[MAXDIMS]; // Used in memory only. Write zero.
     * Ignore on read.
     * 
     * Handle waveNoteH; // Used in memory only. Write zero. Ignore on read.
     * long whUnused[16]; // Reserved. Write zero. Ignore on read.
     * 
     * // The following stuff is considered private to Igor.
     * 
     * short aModified; // Used in memory only. Write zero. Ignore on read.
     * short wModified; // Used in memory only. Write zero. Ignore on read.
     * short swModified; // Used in memory only. Write zero. Ignore on read.
     * 
     * char useBits; // Used in memory only. Write zero. Ignore on read. char
     * kindBits; // Reserved. Write zero. Ignore on read. void **formula; //
     * Used in memory only. Write zero. Ignore on read. long depID; // Used in
     * memory only. Write zero. Ignore on read.
     * 
     * short whpad4; // Reserved. Write zero. Ignore on read. short srcFldr; //
     * Used in memory only. Write zero. Ignore on read. Handle fileName; // Used
     * in memory only. Write zero. Ignore on read.
     * 
     * long **sIndices; // Used in memory only. Write zero. Ignore on read.
     */
    unsignedChecksum = IgorUtil.skip(RESERVED_AT_END, unsignedChecksum, bb);
    assert byteCount() == bb.position() - startPos : byteCount() + " != "
      + (bb.position() - startPos);
    return new Checksummed<>(
      new WaveHeader5(
        unsignedCreationDate, unsignedModDate, npnts, type, unsignedBname, nDim, sfA, sfB,
        unsignedDataUnits, unsignedDimUnits, fsValid, topFullScale, botFullScale),
      (short) unsignedChecksum);
  }

  static int byteCount() {
    return IgorUtil.POINTER_SIZE + 3 * 4 + 2 + 10 + (WaveHeader5.MAX_WAVE_NAME5 + 1) + 4
      + IgorUtil.POINTER_SIZE + IgorUtil.MAXDIMS * 4 + 2 * IgorUtil.MAXDIMS * 8
      + (IgorUtil.MAX_UNIT_CHARS + 1) + IgorUtil.MAXDIMS * (IgorUtil.MAX_UNIT_CHARS + 1) + 2 + 2
      + 2 * 8 + RESERVED_AT_END;
  }

  @Override
  public String toString() {
    return new ReflectionToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).setExcludeFieldNames(
      "unsignedCreationDate", "unsignedModDate", "unsignedBname", "unsignedDataUnits",
      "unsignedDimUnits").append("creationDate", creationDate()).append("modDate", modDate()).append(
      "bname", bname()).append("dataUnits", dataUnits()).append("dimUnits", dimUnits()).build();
  }

  public List<String> dimUnits() {
    return IgorUtil.unsignedBytesListToStringList(unsignedDimUnits);
  }

  @Override
  public WaveHeader5 withBname(String bname) {
    return new WaveHeader5(
      unsignedCreationDate, unsignedModDate, npnts, type, IgorUtil.stringToUnsignedBytes(bname),
      nDim, sfA, sfB, unsignedDataUnits, unsignedDimUnits, fsValid, topFullScale, botFullScale);
  }
}
